Imports System.Text.RegularExpressions
Imports System.Reflection

Namespace Validation

    Public NotInheritable Class StringValidationRules

#Region " Constructors "

        Private Sub New()
        End Sub

#End Region

#Region " Methods "

        Public Shared Function BankRoutingNumberRule(ByVal target As Object, ByVal e As RuleEventArgs) As Boolean

            Dim args As BankRoutingNumberRuleEventArgs = TryCast(e, BankRoutingNumberRuleEventArgs)

            If args Is Nothing Then
                Throw New ArgumentException(String.Format("Wrong type of event args passed to BankRoutingNumberRule.  Should have received CreditCardNumberRule but was passed {0}", e.GetType))
            End If

            Dim objPI As PropertyInfo = target.GetType().GetProperty(args.PropertyName)

            If Not objPI.PropertyType Is GetType(System.String) Then
                Throw New NotSupportedException("Bank routing number validation rule can only be applied to String properties.")
            End If

            Dim strBankRoutingNumber As String = CType(objPI.GetValue(target, Nothing), String)

            If args.RequiredEntry = RequiredEntry.Yes Then

                If String.IsNullOrEmpty(strBankRoutingNumber) OrElse IsDBNull(strBankRoutingNumber) Then
                    e.BrokenRuleDescription = String.Format("{0} was null or empty but is a required field.", RuleEventArgs.GetPropertyFriendlyName(e))
                    Return False
                End If

            Else

                If String.IsNullOrEmpty(strBankRoutingNumber) OrElse IsDBNull(strBankRoutingNumber) Then
                    Return True
                End If

            End If

            Dim intLengthBankRoutingNumber As Integer = Len(strBankRoutingNumber)
            Dim intValue As Integer

            If intLengthBankRoutingNumber <> 9 Then
                args.BrokenRuleDescription = String.Format("The entered value {0} is not a valid bank routing number.  All bank routing numbers are 9 digits in length.", strBankRoutingNumber)
                Return False
            End If

            If Integer.Parse(strBankRoutingNumber.Substring(0, 1)) > 1 Then
                args.BrokenRuleDescription = String.Format("The entered value {0} is not a valid bank routing number.  The first digit must be a 0 or a 1.", strBankRoutingNumber)
                Return False
            End If

            For Each c As Char In strBankRoutingNumber

                If Not Char.IsDigit(c) Then
                    args.BrokenRuleDescription = String.Format("The entered value {0} is not a valid bank routing number.  Only numeric input is allowed.", strBankRoutingNumber)
                    Return False
                End If

            Next

            For intX As Integer = 0 To 8 Step 3
                intValue += Integer.Parse(strBankRoutingNumber.Substring(intX, 1)) * 3
                intValue += Integer.Parse(strBankRoutingNumber.Substring(intX + 1, 1)) * 7
                intValue += Integer.Parse(strBankRoutingNumber.Substring(intX + 2, 1))
            Next

            If intValue Mod 10 <> 0 Then
                args.BrokenRuleDescription = String.Format("The entered value {0} is not a valid bank routing number.", strBankRoutingNumber)
                Return False

            Else
                Return True
            End If

        End Function

        Public Shared Function CreditCardNumberRule(ByVal target As Object, ByVal e As RuleEventArgs) As Boolean

            Dim args As CreditCardNumberRuleEventArgs = TryCast(e, CreditCardNumberRuleEventArgs)

            If args Is Nothing Then
                Throw New ArgumentException(String.Format("Wrong type of event args passed to RegularExpressionRule.  Should have received CreditCardNumberRule but was passed {0}", e.GetType))
            End If

            Dim objPI As PropertyInfo = target.GetType().GetProperty(args.PropertyName)

            If Not objPI.PropertyType Is GetType(System.String) Then
                Throw New NotSupportedException("Credit card number validation rule can only be applied to String properties.")
            End If

            Dim strCardNumber As String = CType(objPI.GetValue(target, Nothing), String)

            If args.RequiredEntry = RequiredEntry.Yes Then

                If String.IsNullOrEmpty(strCardNumber) OrElse IsDBNull(strCardNumber) Then
                    e.BrokenRuleDescription = String.Format("{0} was null or empty but is a required field.", RuleEventArgs.GetPropertyFriendlyName(e))
                    Return False
                End If

            Else

                If String.IsNullOrEmpty(strCardNumber) OrElse IsDBNull(strCardNumber) Then
                    Return True
                End If

            End If

            Dim intLengthCreditCardNumber As Integer = Len(strCardNumber)
            Dim intArr(intLengthCreditCardNumber) As Integer
            Dim intArrValue As Integer
            Dim intStart As Integer
            Dim intValue As Integer

            For Each c As Char In strCardNumber

                If Not Char.IsDigit(c) Then
                    args.BrokenRuleDescription = String.Format("The entered value {0} is not a valid credit card number.  Only numeric input is allowed.", strCardNumber)
                    Return False
                End If

            Next

            For intCount = intLengthCreditCardNumber - 1 To 1 Step -2
                intValue = CType(Mid(strCardNumber, intCount, 1), Integer) * 2
                intArr(intCount) = intValue
            Next intCount

            intValue = 0

            If intLengthCreditCardNumber Mod 2 = 0 Then
                intStart = 2

            Else
                intStart = 1
            End If

            For intCount = intStart To intLengthCreditCardNumber Step 2
                intValue = intValue + CType(Mid(strCardNumber, intCount, 1), Integer)
                intArrValue = intArr(intCount - 1)

                If intArrValue < 10 Then
                    intValue = intValue + intArrValue

                Else
                    intValue = intValue + CType(Left(CType(intArrValue, String), 1), Integer) + CType(Right(CType(intArrValue, String), 1), Integer)
                End If

            Next intCount

            If intValue Mod 10 <> 0 Then
                args.BrokenRuleDescription = String.Format("The entered value {0} is not a valid credit card number.", strCardNumber)
                Return False

            Else
                Return True
            End If

        End Function

        Public Shared Function IsRegularExpressionPatternValid(ByVal strRegularExpressionPattern As String) As Boolean

            Dim bolReturnValue As Boolean = False

            Try

                Dim objRegEx As New System.Text.RegularExpressions.Regex(strRegularExpressionPattern)
                bolReturnValue = True

            Catch ex As Exception
            End Try

            Return bolReturnValue
        End Function

        Public Shared Function RegularExpressionRule(ByVal target As Object, ByVal e As RuleEventArgs) As Boolean

            'great site for patterns
            'http://regexlib.com/Search.aspx
            Dim args As RegulatExpressionRuleEventArgs = TryCast(e, RegulatExpressionRuleEventArgs)

            If args Is Nothing Then
                Throw New ArgumentException(String.Format("Wrong type of event args passed to RegularExpressionRule.  Should have received RegulatExpressionRuleEventArgs but was passed {0}", e.GetType))
            End If

            Dim objPI As PropertyInfo = target.GetType().GetProperty(args.PropertyName)
            Dim strTestMe As String = CType(objPI.GetValue(target, Nothing), String)

            If args.RegularExpressionPatternType = RegularExpressionPatternType.Custom AndAlso String.IsNullOrEmpty(args.CustomRegularExpressionPattern) Then
                Throw New ArgumentOutOfRangeException("CustomRegularExpressionPattern", "Programmer did not supply the CustomRegularExpressionPattern")

            ElseIf Not IsRegularExpressionPatternValid(args.CustomRegularExpressionPattern) Then
                Throw New ArgumentOutOfRangeException("CustomRegularExpressionPattern", "Programmer did not supply a valid CustomRegularExpressionPattern")

            ElseIf args.RequiredEntry = RequiredEntry.Yes Then

                If strTestMe Is Nothing OrElse IsDBNull(strTestMe) OrElse strTestMe.Trim.Length = 0 Then
                    e.BrokenRuleDescription = String.Format("{0} was null or empty but is a required field.", RuleEventArgs.GetPropertyFriendlyName(e))
                    Return False
                End If

            Else

                If strTestMe Is Nothing OrElse strTestMe.Trim.Length = 0 Then
                    Return True
                End If

            End If

            Dim strPattern As String = String.Empty
            Dim strBrokenRuleDescription As String = String.Empty

            Select Case args.RegularExpressionPatternType

                Case RegularExpressionPatternType.Custom
                    strPattern = args.CustomRegularExpressionPattern
                    strBrokenRuleDescription = String.Format("{0} did not match the required {1} pattern.", RuleEventArgs.GetPropertyFriendlyName(e), strPattern)

                Case RegularExpressionPatternType.Email
                    strPattern = "^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"
                    strBrokenRuleDescription = String.Format("{0} did not match the required email pattern.", RuleEventArgs.GetPropertyFriendlyName(e))

                Case RegularExpressionPatternType.IPAddress
                    strPattern = "^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$"
                    strBrokenRuleDescription = String.Format("{0} did not match the required IP Address pattern.", RuleEventArgs.GetPropertyFriendlyName(e))

                Case RegularExpressionPatternType.SSN
                    strPattern = "^\d{3}-\d{2}-\d{4}$"
                    strBrokenRuleDescription = String.Format("{0} did not match the required SSN pattern.", RuleEventArgs.GetPropertyFriendlyName(e))

                Case RegularExpressionPatternType.URL
                    strPattern = "(?#WebOrIP)((?#protocol)((news|nntp|telnet|http|ftp|https|ftps|sftp):\/\/)?(?#subDomain)(([a-zA-Z0-9]+\.*(?#domain)[a-zA-Z0-9\-]+(?#TLD)(\.[a-zA-Z]+){1,2})|(?#IPAddress)((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])))+(?#Port)(:[1-9][0-9]*)?)+(?#Path)((\/((?#dirOrFileName)[a-zA-Z0-9_\-\%\~\+]+)?)*)?(?#extension)(\.([a-zA-Z0-9_]+))?(?#parameters)(\?([a-zA-Z0-9_\-]+\=[a-z-A-Z0-9_\-\%\~\+]+)?(?#additionalParameters)(\&([a-zA-Z0-9_\-]+\=[a-z-A-Z0-9_\-\%\~\+]+)?)*)?"
                    strBrokenRuleDescription = String.Format("{0} did not match the required URL pattern.", RuleEventArgs.GetPropertyFriendlyName(e))

                Case RegularExpressionPatternType.ZipCode
                    strPattern = "^\d{5}(-\d{4})?$"
                    strBrokenRuleDescription = String.Format("{0} did not match the required Zip Code pattern.", RuleEventArgs.GetPropertyFriendlyName(e))

                Case Else
                    Throw New ArgumentOutOfRangeException("RegularExpressionPatternType", "Programmer did not program this pattern type")
            End Select

            If Regex.IsMatch(strTestMe, strPattern, RegexOptions.IgnoreCase) Then
                Return True

            Else
                e.BrokenRuleDescription = strBrokenRuleDescription
                Return False
            End If

        End Function

        Public Shared Function StringLengthRule(ByVal target As Object, ByVal e As RuleEventArgs) As Boolean

            Dim args As StringLengthRuleEventArgs = DirectCast(e, StringLengthRuleEventArgs)

            If args Is Nothing Then
                Throw New ArgumentException(String.Format("Wrong type of event args passed to StringLengthRule.  Should have received StringLengthRuleEventArgs but was passed {0}", e.GetType))
            End If

            Dim objPI As PropertyInfo = target.GetType().GetProperty(args.PropertyName)
            Dim strTestMe As String = CType(objPI.GetValue(target, Nothing), String)

            If args.AllowNullString = AllowNullString.No AndAlso (IsDBNull(strTestMe) OrElse strTestMe Is Nothing) Then
                e.BrokenRuleDescription = String.Format("{0} can not be null.", RuleEventArgs.GetPropertyFriendlyName(e))
                Return False

            ElseIf args.AllowNullString = AllowNullString.Yes AndAlso (strTestMe Is Nothing OrElse IsDBNull(strTestMe)) Then
                Return True
            End If

            If IsDBNull(strTestMe) OrElse String.IsNullOrEmpty(strTestMe) Then
                strTestMe = String.Empty
            End If

            If args.MinimumLength > 0 AndAlso strTestMe.Length < args.MinimumLength Then
                e.BrokenRuleDescription = String.Format("{0} can not be less than {1} character(s) long.", RuleEventArgs.GetPropertyFriendlyName(e), args.MinimumLength.ToString)
                Return False

            ElseIf args.MaximumLength > 0 AndAlso strTestMe.Length > args.MaximumLength Then
                e.BrokenRuleDescription = String.Format("{0} can not be greater than {1} character(s) long.", RuleEventArgs.GetPropertyFriendlyName(e), args.MaximumLength.ToString)
                Return False
            End If

            Return True
        End Function

#End Region

    End Class

End Namespace
